;   Copyright 2011 Jason Feingold
;
;   Licensed under the Apache License, Version 2.0 (the "License");
;   you may not use this file except in compliance with the License.
;   You may obtain a copy of the License at;
;
;       http://www.apache.org/licenses/LICENSE-2.0
;
;   Unless required by applicable law or agreed to in writing, software
;   distributed under the License is distributed on an "AS IS" BASIS,
;   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;   See the License for the specific language governing permissions and
;   limitations under the License.;

;
; S-Expr Builder
; This builds the S-Expr project.
;

; define a namespace and import clobr
(ns sexpr-builder
	(:refer clobr))

(def project-name "s-expressions")
(def version-major 1)
(def version-minor 0)

(def full-name (str project-name "-" version-major "." version-minor))
(def dist-name (str full-name ".zip"))

; A banner is always nice.
(println (str "---\n--- Project: " full-name "\n---"))

; Set log level early.
(set-log-level :TERSE)

; Get the command line args: we have some special processing to do before running the build.
(def args (argv *command-line-args*))
; Allow user to set the log level.  This must come first (if at all).
(set-log-level-from-args args)

; some generally useful file filters.

(defn -svn [f] (-a (-pnx "svn") f))

; change this for easy relocation.
(def root-dir (mk-path "."))
(assert-dir root-dir)

; Define a bunch of paths that will be needed throughout.
(def rsrc-dir (mk-path root-dir "resources"))
(def source-dir (mk-path "source"))
(def source-main-dir (mk-path source-dir "main"))
(def source-unit-dir (mk-path source-dir "unit"))
(def bin-dir (mk-path root-dir "bin"))
(def build-dir (mk-path root-dir "build"))
(def libs-dir (mk-path build-dir "libs"))
(def libs-main-dir (mk-path libs-dir "main"))
(def libs-ref-dir (mk-path libs-dir "ref"))
(def libs-unit-dir (mk-path libs-dir "unit"))
(def target-dir (mk-path root-dir "build" "clobr"))
(def classes-dir (mk-path target-dir "classes"))
(def classes-main-dir (mk-path classes-dir "main"))
(def classes-unit-dir (mk-path classes-dir "unit"))

; show the result of a file filter applied to a directory.
(defn print-files [root filter]
	(println (str "! show filter: " root))
	(doseq [file (list-files root filter)] (println (str file))))
		
(def resources-dir (mk-path "resources"))

; extracts multiple files from a resource zip to a directory
(defn lib-extract [dir, zip-name, entries]
	(unzip (mk-path resources-dir zip-name) [(zip-extract dir entries)]))
	
; extractors for each resource.

(def scalatest-zip "scalatest-1.6.1.zip")
(defn lib-scalatest [dir] (lib-extract dir scalatest-zip ["scalatest-1.6.1/scalatest-1.6.1.jar"]))

(def scala-zip "scala-2.9.1.final.zip")
(defn lib-scala [dir] (lib-extract dir scala-zip ["scala-2.9.1.final/lib/scala-library.jar"]))

; targets

; target: clean the build artifacts.
(def target-clean (target
	"cleans the build artifacts"
	{ "all" nil       ; cleans the entire build-dir
	, "libs" nil      ; also cleans the libs
	, "resources" nil ; also cleans the resources
	}
	(fn [args]
		(if (contains? args "all")
			(delete-dir build-dir)
			(do
				(when (contains? args "libs")
					(delete-dir libs-dir))
				(delete-dir target-dir)))
		(when (contains? args "resources")
			(delete-dir rsrc-dir)))
	))

; target: download resources.
(def target-resources (target
	"download resources"
	{"clean" nil}
	(fn [args]
		; file-name exists in rsrc dir or download it from url.
		(defn update-rsrc [file-name url]
			(def rsrc (mk-path rsrc-dir file-name))
			(if (.isFile rsrc)
				(log :VERBOSE (str "resource up-to-date: " file-name))
				(download rsrc-dir [(str url file-name)])))
		(if (not (.exists rsrc-dir))
			(create-dir rsrc-dir)
			(when (contains? args "clean")
				(refresh-dir rsrc-dir)))
		(update-rsrc scala-zip "http://www.scala-lang.org/downloads/distrib/files/")
		(update-rsrc scalatest-zip "http://www.artima.com/downloadScalaTest/"))
	))

; target: extract the libs from the resources.
(def target-libs (target
	"extract libs from the resources"
	{"clean" nil}
	(fn [args]
		(when (contains? args "clean")
			(delete-dir libs-dir))
		; main libs
		(create-dir libs-main-dir)
		; ref libs
		(create-dir libs-ref-dir)
		(lib-scala libs-ref-dir)
		; unit libs
		(create-dir libs-unit-dir)
		(lib-scalatest libs-unit-dir))
	))

; target: compile the code and jar it.
(def target-build (target
	"compile the code and jar it."
	{"clean" nil, "opt" true}
	(fn [args]
		(def jar-name (str project-name ".jar"))
		(def jar-file (mk-path target-dir jar-name))
		(def optimise (as-bool (get args "opt" "off")))
		(println (str "[ optimised: " optimise " ]"))
		(when (contains? args "clean")
			(refresh-dir classes-dir)
			(delete-file jar-file))
		(def rebuild-main (or (not (.exists classes-main-dir))
			(check-depends [jar-file] (ls source-main-dir (-o -d (-px "scala"))))))
		(def rebuild-unit (or rebuild-main (not (.exists classes-unit-dir))
			(check-depends (ls (create-dir classes-unit-dir) (-o -d (-px "class"))) 
			(ls source-unit-dir (-o -d (-px "scala"))))))
		(def cp-main (ls libs-main-dir (-px "jar")))
		; compile main
		(print "--- compile: main: ")
		(if rebuild-main
			(do
				(println (str (count (ls source-main-dir (-o -d (-px "scala")))) " source file(s)."))
				(refresh-dir classes-main-dir)
				; delete this first so that a failed build doesn't leave an old jar behind.
				(when (.exists jar-file) (delete-file jar-file))
				(scalac optimise false cp-main source-main-dir (create-dir classes-main-dir)))
			(println "up to date."))
		; compile unit
		(print "--- compile: unit: ")
		(if rebuild-unit
			(do
				(println (str (count (ls source-unit-dir (-o -d (-px "scala")))) " source file(s)."))
				(refresh-dir classes-unit-dir)
				(def cp-unit (cons classes-main-dir (concat cp-main (ls libs-unit-dir (-px "jar")))))
				(scalac optimise false cp-unit source-unit-dir (create-dir classes-unit-dir)))
			(println "up to date."))
		; create the artifact
		(delete-file-maybe jar-file)
		(zip jar-file
			[ (zip-insert-fileset "" classes-main-dir (-o -d (-px "class")))
			, (zip-insert-fileset "" source-main-dir (-svn (-pnx "scala")))
			]))
	))
	
; target: run unit tests
(def target-unit (target
	"run unit tests"
	{}
	(fn [args]
		(try
			(java ["-Xmx1g"] 
				(reduce #(concat %1 (ls %2 (-px "jar"))) [classes-unit-dir] [target-dir libs-main-dir libs-ref-dir libs-unit-dir])
				"org.scalatest.tools.Runner" ["-p" "." "-oWD" "-s" "sexpr.MasterSuite"])
			(catch Throwable e
				(println "ERROR: Unit Tests Failed"))))
	))

; target: zip project distribution.
(def target-dist (target
	"zip up project sources"
	nil
	(fn [args]
		(def jar-name (str project-name ".jar"))
		(def dist-file (mk-path target-dir dist-name))
		(delete-file dist-file)
		(zip dist-file
			[ (zip-insert-fileset "source" source-dir (-svn (-px "scala")))
			, (zip-insert-fileset "." (mk-path "build/clobr") (-p jar-name))
			, (zip-insert-fileset "" (mk-path ".") (-o
				(-p ".classpath")
				(-p ".project")
				(-p "build.clj")))
			]))
	))

; target: for ad hoc testing
(def target-test-1 (target
	"for ad hoc testing"
	nil
	(fn [args]
		(print-files source-dir (-o -d -f)))
	))
	
; Here, we associated each target with its name and let the builder process the command line.
(run-build args
	{ "test-1" target-test-1
	, "clean" target-clean
	, "resources" target-resources
	, "libs" target-libs
	, "build" target-build
	, "unit" target-unit
	, "dist" target-dist
	})

; [END]
